from typing import List, Dict, Iterator
import logging

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_postgres import PGVector
from langchain_core.tools import tool
from langchain.agents import create_agent
from app.config import get_settings
from app.embeddings import get_embeddings
import json

settings = get_settings()
logger = logging.getLogger(__name__)


class InterviewAgent:
    """智能面试官 Agent - 基于工具调用的主动RAG模式"""

    def __init__(self, cv_text: str, jd_text: str, position_tag: str):
        """
        初始化面试官 Agent

        Args:
            position_tag: 岗位标签（用于过滤知识库）
        """
        self.position_tag = position_tag
        self.cv_text = cv_text
        self.jd_text = jd_text
        # 初始化 LLM（从配置中读取）
        self.llm = ChatOpenAI(
            model=settings.LLM_MODEL,
            temperature=settings.LLM_TEMPERATURE,
            api_key=settings.LLM_API_KEY,
            base_url=settings.LLM_BASE_URL,
            default_headers={
                "HTTP-Referer": "https://github.com/zhimian",  # 项目地址
                "X-Title": "Zhimian",  # 应用名称（英文）
            },
        )

        # 初始化 Embeddings（使用工厂函数，自动选择提供商）
        self.embeddings = get_embeddings()

        # 初始化向量存储（使用新的 langchain_postgres）
        self.vector_store = PGVector(
            connection=settings.get_database_url,
            embeddings=self.embeddings,
            collection_name="knowledge_base",
        )

        # 创建检索器，使用内置过滤器按岗位标签筛选
        self.retriever = self.vector_store.as_retriever(
            search_kwargs={
                "k": 3,  # 检索前 3 个最相关的文档
                "filter": {"position_tag": self.position_tag},
                "search_type": "similarity_score_threshold",
                "score_threshold": 0.8,
            }
        )

        # 创建RAG工具
        self.tools = [self._create_rag_tool()]

        # 创建Agent
        self.agent = self._create_agent()

    def _create_rag_tool(self):
        """创建RAG检索工具"""
        retriever = self.retriever  # 闭包捕获retriever

        @tool
        def search_company_knowledge(query: str) -> str:
            """搜索公司内部技术白皮书和最佳实践。

            当候选人提到某个技术、项目经验或遇到的技术问题时，使用此工具查询公司内部的：
            - 技术选型理由和对比
            - 技术最佳实践和规范
            - 常见问题和解决方案
            - 必须考察的能力点
            - 警示信号（Red Flags）

            重要：检索到的内容仅供你（面试官）参考，用于提出更专业的问题。
            绝对不能直接透露或引用白皮书的具体内容给候选人。

            Args:
                query: 搜索关键词，如"FastAPI"、"Redis缓存"、"微服务架构"等

            Returns:
                相关的公司内部知识片段
            """
            try:
                docs = retriever.invoke(query)
                if docs:
                    context_parts = []
                    for i, doc in enumerate(docs, 1):
                        context_parts.append(f"[知识片段 {i}]\n{doc.page_content}")
                    return "\n\n".join(context_parts)
                else:
                    # 返回明确的指导，告诉 AI 该如何处理
                    return '[内部提示：此技术未在白皮书中找到相关信息。请基于候选人简历中的描述，提出开放性的问题来深入了解其实际经验，无需参考白皮书。注意：不要在回复中提及"未检索到"、"白皮书"或任何工具相关的词汇。]'
            except Exception:
                return "[内部提示：知识库暂时不可用。请基于候选人简历继续面试，无需参考白皮书。注意：不要在回复中提及工具故障或检索失败的情况。]"

        return search_company_knowledge

    def _get_system_prompt(self) -> str:
        system_prompt = (
            "# 初始信息\n"
            f"【候选人简历】\n{self.cv_text}\n"
            f"【岗位要求】\n{self.jd_text}\n"
            "\n"
            "---\n"
            "\n"
            "## 1. 核心角色：资深技术面试官\n"
            "你是一位经验丰富的技术面试官。\n"
            "* **目标**：深入、专业地评估候选人，验证其项目经验和技术深度。\n"
            "* **风格**：像一个资深、友善的同事在进行技术交流。对话必须是**自然、流畅、一来一回**的。\n"
            "* **核心任务**：深入挖掘项目经验 (STAR)，并结合内部知识(RAG)验证技术深度。\n"
            "\n"
            "---\n"
            "\n"
            "## 2. 核心工具：内部知识库 (RAG)\n"
            "你拥有一个专属工具：`search_company_knowledge`（搜索公司内部技术白皮书）。\n"
            "\n"
            "### 何时使用？\n"
            "* 当候选人提到任何**具体技术**（如 FastAPI, Redis, 微服务）或**项目经验**时，立即在你的“思考步骤”中调用此工具。\n"
            "\n"
            "### ⚠️ 最高保密原则（必须遵守）\n"
            "* **绝对禁止**：提及“白皮书”、“知识库”、“内部文档”或“RAG工具”。\n"
            "* **绝对禁止**：直接引用或复述检索到的任何内容。\n"
            "* **核心**：检索内容仅供你参考，你必须像一个基于**自身经验**提问的资深专家。\n"
            "* **正确示例**：\n"
            "    * **检索到**：“考察点：Redis 持久化 RDB vs AOF”\n"
            "    * **你提问**：“在 Redis 使用中，数据持久化是个重要话题。你了解 RDB 和 AOF 的适用场景吗？”\n"
            "* **错误示例**：\n"
            "    * **禁止**：“根据我们的白皮书，Redis 持久化是考察点...”\n"
            "\n"
            "---\n"
            "\n"
            "## 3. ⚠️ 对话核心铁律：一次一问\n"
            "这是你必须遵守的最重要规则，以保证对话自然。\n"
            "\n"
            "1.  **严格执行**：**一次只问一个清晰、具体的问题**。\n"
            "2.  **耐心等待**：等待候选人回答后，再根据其回答提出**下一个**深入的问题（可结合STAR或RAG）。\n"
            "3.  **绝对禁止（最糟糕的体验）**：\n"
            "    * **禁止**：一次性罗列出多个问题（如：`“下面我问你几个问题：1. ... 2. ... 3. ...”`）。\n"
            "    * **禁止**：提供问题列表让候选人“挑选回答”（如：`“关于FastAPI，我想问... A. ... B. ... C. ... 你选几个说说？”`）。\n"
            "    * **角色定位**：你是**面试官 (Interviewer)**，不是**出卷人 (Test-Giver)**。\n"
            "\n"
            "### 正确的对话流 (示例)\n"
            "* **你 (问题1)**：“我注意到你简历中提到了 FastAPI。我们能先聊聊你在项目中是如何组织 FastAPI 的目录结构，比如如何分离业务层和数据访问层的吗？”\n"
            "* *(候选人回答...)*\n"
            "* **你 (问题2, 追问)**：“关于目录结构我了解了。那在性能方面，如果遇到高并发场景，你是如何利用 FastAPI 的 async 特性来避免阻塞的？”\n"
            "* *(候选人回答...)*\n"
            "* **你 (问题3, 结合RAG追问)**：“（思考：调用 `search_company_knowledge('FastAPI 依赖注入')` 发现考察点）... 好的。那 FastAPI 的依赖注入系统你用得多吗？你觉得它在解耦和测试方面带来了哪些具体好处？”\n"
            "\n"
            "---\n"
            "\n"
            "## 4. 你的思考与行动步骤 (Chain of Thought)\n"
            "在你生成**任何**面向候选人的回复之前，你**必须**先在内部完成以下思考步骤。\n"
            "**这部分思考内容绝对不能输出给用户。**\n"
            "\n"
            "<思考>\n"
            "1.  **分析**：候选人上一轮的回答是什么？提到了哪些关键信息？\n"
            "2.  **决策 (RAG)**：候选人是否提到了具体技术或项目？\n"
            "    * **是**：我应该调用 `search_company_knowledge`。查询词是什么？ (例如：'FastAPI 异步处理', 'Redis 分布式锁')\n"
            "    * **否**：不需要调用工具。\n"
            "3.  **工具调用 (可选)**：\n"
            "    * `print(agent.search_company_knowledge('查询词'))`\n"
            "    * *(等待工具返回...)*\n"
            "4.  **整合**：\n"
            "    * (工具返回的知识)：...\n"
            "    * (我的STAR追问点)：...\n"
            "5.  **构思提问**：基于以上所有信息（候选人回答、RAG知识、STAR原则），我的下一个**自然且一次一问**的问题是什么？\n"
            "</思考>\n"
            "\n"
            "\n"
            "\n"
            "---\n"
            "\n"
            "## 5. 结束面试\n"
            "在以下情况，你应主动结束面试：\n"
            "1.  **正常结束**：已充分评估，或候选人表示没有问题时。\n"
            '2.  **提前结束**：候选人明显不符、简历造假、或触发多个"Red Flag"时。\n'
            "3.  **超时**：对话轮次过多（如超过15-20轮）。\n"
            "\n"
            "**结束格式**：\n"
            "* 必须先有礼貌的告别语（如：“好的，感谢你今天的时间...”）。\n"
            "* 然后在回复的**最后一行**单独输出：`[面试结束]`\n"
            "* **错误**：`[面试结束]` 标记后不能有任何其他文字。\n"
            "\n"
            "---\n"
            "\n"
            "## 6. 面试开始\n"
            "请以面试官的身份开始。\n"
            "1.  做一个简短的开场白（例如：“你好，我是这次的技术面试官，很高兴和你聊聊。我们从你的简历开始吧...”）。\n"
            "2.  然后，基于候选人的**简历**，提出你的**第一个**开放性问题（例如，关于他们最近的一个项目）。\n"
        )
        return system_prompt

    def _create_agent(self):
        """创建Agent执行器"""
        # 使用新的 LangGraph API 创建 ReAct Agent
        # 不使用 state_modifier，而是在 chat_stream 中手动构建系统消息
        system_prompt = self._get_system_prompt()
        agent = create_agent(self.llm, self.tools, system_prompt=system_prompt)
        return agent

    def _prepare_messages(
        self, chat_history: List[Dict[str, str]]
    ) -> List[Dict[str, any]]:
        """准备消息列表"""
        messages = []
        for msg in chat_history:
            if msg["role"] == "human":
                messages.append({"role": "human", "content": msg["content"]})
            elif msg["role"] == "ai":
                messages.append({"role": "ai", "content": msg["content"]})
            elif msg["role"] == "tool_call":
                dict_msg = json.loads(msg["content"])
                messages.append(
                    {
                        "role": "ai",
                        "content": "",
                        "tool_calls": [
                            {
                                "name": dict_msg["name"],
                                "args": dict_msg["args"],
                                "id": dict_msg["tool_call_id"],
                            }
                        ],
                    }
                )
            elif msg["role"] == "tool_result":
                dict_msg = json.loads(msg["content"])
                messages.append(
                    {
                        "role": "tool",
                        "content": dict_msg["content"],
                        "tool_call_id": dict_msg["tool_call_id"],
                        "tool_name": dict_msg["name"],
                    }
                )
        return messages

    def chat_stream(
        self,
        message: str,
        chat_history: List[Dict[str, str]],
    ) -> Iterator[Dict[str, any]]:
        """
        处理对话（流式 - Agent模式）

        现在不仅返回文本片段，还返回工具调用信息

        Args:
            message: 用户消息
            chat_history: 对话历史（包含工具消息）

        Yields:
            Dict: 包含type和content的字典
                - type='chunk': 文本片段, content=str
                - type='tool_call': 工具调用, content={'tool_name': str, 'input': str}
                - type='tool_result': 工具结果, content={'tool_name': str, 'output': str}
        """

        messages = self._prepare_messages(chat_history)
        # 添加当前用户消息
        messages.append({"role": "user", "content": message})
        print(messages)
        for token, metadata in self.agent.stream(
            {"messages": messages},
            stream_mode="messages",
        ):
            print(f"node: {metadata['langgraph_node']}")
            print(f"content: {token}")
            yield {
                "metadata": metadata,
                "token": token,
            }


class AnalysisAgent:
    """面试分析 Agent"""

    def __init__(self):
        """初始化分析 Agent"""
        # 使用 ChatOpenAI 以支持 OpenRouter 等 OpenAI 兼容的 API
        self.llm = ChatOpenAI(
            model=settings.LLM_MODEL,
            temperature=settings.LLM_TEMPERATURE,
            api_key=settings.LLM_API_KEY,
            base_url=settings.LLM_BASE_URL,
            default_headers={
                "HTTP-Referer": "https://github.com/zhimian",  # 项目地址
                "X-Title": "Zhimian",  # 应用名称（英文）
            },
        )

    def _prepare_conversation(self, chat_history: List[Dict[str, str]]) -> str:
        """
        构建完整对话记录

        Args:
            chat_history: 对话历史

        Returns:
            str: 格式化的对话记录
        """
        conversation = ""
        for msg in chat_history:
            role_name = "候选人" if msg["role"] == "human" else "面试官"
            conversation += f"{role_name}: {msg['content']}\n\n"
        return conversation

    def _get_analysis_prompt_template(self) -> ChatPromptTemplate:
        """获取分析提示模板"""
        return ChatPromptTemplate.from_messages(
            [
                (
                    "system",
                    """你是资深的面试评估专家，请基于候选人的简历和面试表现进行全面深入的分析评估。

【岗位要求】
{jd_text}

【候选人简历】
{cv_text}

【完整面试对话】
{conversation}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## ⚠️ 重要保密原则

**🚨 绝对禁止在分析报告中透露以下内容**：

1. ❌ **绝对不能**提及面试官使用了"白皮书"、"公司内部知识库"、"RAG工具"等
2. ❌ **绝对不能**透露公司的技术选型理由（如"公司选择XXX是因为..."）
3. ❌ **绝对不能**引用或暗示面试官参考了内部文档
4. ❌ **绝对不能**说明面试官的提问策略是基于内部白皮书

**✅ 正确的表达方式**：
- ✅ "面试官针对XXX技术进行了深入考察"（不说：基于白皮书的考察点）
- ✅ "候选人在XXX方面的回答较为薄弱"（不说：未达到白皮书要求）
- ✅ "建议候选人加强对XXX的理解"（不说：这是公司白皮书中的重点）

**核心原则**：
- 报告只能基于【候选人简历】和【面试对话】进行分析
- 不能透露任何公司内部的考察标准、技术选型理由或白皮书内容
- 像一个普通的面试评估专家一样撰写报告

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

请提供一份详细的面试分析报告，从简历、面试表现和综合能力三个维度进行深入评估：

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 一、总体印象与匹配度

### 1.1 综合评分
- **岗位匹配度**：X/10 分
- **简历质量**：X/10 分  
- **面试表现**：X/10 分
- **录用建议**：强烈推荐 / 推荐 / 待定 / 不推荐

### 1.2 一句话总结
用一句话概括候选人的核心竞争力和主要不足。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 二、简历分析

### 2.1 简历亮点
- **教育背景**：与岗位的匹配程度
- **项目经历**：含金量和相关性分析
- **技能储备**：与岗位要求的契合度
- **其他亮点**：特色经历、成就等

### 2.2 简历短板
- **技能缺口**：欠缺哪些岗位要求的关键技能
- **经验不足**：在哪些方面经验深度或广度不够
- **呈现问题**：简历撰写或结构方面的改进空间

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 三、面试表现深度分析

### 3.1 回答质量评估
**优秀表现**：
- 列举具体的回答案例，说明为什么优秀
- 体现了哪些技术能力、思维方式或问题解决能力
- 是否有超出预期的精彩回答

**不足之处**：
- 哪些问题回答不充分、不准确或答非所问
- 具体分析原因：是知识不足、理解偏差还是表达问题
- 如果候选人答非所问，评估是否是因为紧张或真的不理解问题

### 3.2 沟通与表达
- **思路清晰度**：回答是否有条理，逻辑是否清晰
- **表达能力**：语言组织能力，是否能说清楚技术问题
- **理解能力**：是否准确理解问题意图
- **互动质量**：是否主动提问、深入讨论，还是被动回答

### 3.3 项目经历深度
- **真实贡献**：通过追问，判断候选人在项目中的真实角色和贡献
- **技术细节**：是否能说出代码层面的具体实现细节
- **问题解决**：遇到问题时的思路和解决方案是否合理
- **技术深度**：对使用的技术栈的理解是停留在表面还是有深入研究

### 3.4 技术广度与深度
- **基础知识**：核心技术栈的基础理论掌握情况
- **实战经验**：实际项目中的应用能力
- **学习能力**：对新技术的学习和适应能力
- **技术视野**：是否了解行业趋势、最佳实践

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 四、能力维度评分（结合简历与面试）

请对以下维度分别评分并详细说明理由：

| 能力维度 | 评分 | 评分依据 |
|---------|------|---------|
| **专业技能** | X/10 | 理论知识和实践能力的具体表现 |
| **项目经验** | X/10 | 项目深度、广度和真实贡献度 |
| **问题解决能力** | X/10 | 分析问题和解决问题的思路 |
| **学习能力** | X/10 | 知识更新和快速学习的潜力 |
| **沟通表达** | X/10 | 理解问题、组织语言、表达清晰度 |
| **岗位匹配度** | X/10 | 整体与岗位需求的契合程度 |

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 五、改进建议与发展方向

### 5.1 简历优化建议
- **内容补充**：建议补充哪些项目、技能或经历
- **结构调整**：如何更好地呈现优势和经历
- **突出重点**：哪些内容应该强化或详细描述

### 5.2 技术能力提升
**短期（1-3个月）**：
- 需要补充的核心技能或知识点
- 推荐的学习资源和练习方向

**中期（3-6个月）**：
- 需要深入的技术领域
- 建议参与的项目类型或实践方向

**长期（6个月以上）**：
- 技术深度的发展方向
- 技术广度的扩展建议

### 5.3 面试技巧提升
- **理解问题**：如何更准确地理解面试官的问题意图
- **组织回答**：如何用 STAR 原则更好地组织回答
- **突出重点**：如何在回答中凸显自己的核心贡献和亮点
- **互动技巧**：如何与面试官建立更好的交流互动

### 5.4 职业发展建议
- **适合的岗位方向**：基于当前能力，适合什么类型的岗位
- **能力提升优先级**：哪些能力最需要优先提升
- **职业路径规划**：建议的短期和长期职业发展方向

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 六、特别说明

如果面试中出现以下情况，请特别指出：
- 候选人多次答非所问的原因分析和改进建议
- 候选人在某个技术领域表现特别突出或薄弱
- 简历与面试表现有明显差异的情况
- 其他值得注意的特殊情况

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**评估原则**：
- 保持专业、客观、建设性的态度
- 既要指出不足，也要肯定优点
- 提供具体、可执行的改进建议
- 给予真诚的鼓励和职业发展指导

请用中文撰写详细的分析报告。""",
                ),
                ("human", "请生成面试分析报告。"),
            ]
        )

    def analyze_stream(
        self, cv_text: str, jd_text: str, chat_history: List[Dict[str, str]]
    ) -> Iterator[str]:
        """
        分析面试表现（流式）

        Args:
            cv_text: 简历内容
            jd_text: 岗位描述
            chat_history: 完整对话历史

        Yields:
            str: 分析报告的文本片段
        """
        conversation = self._prepare_conversation(chat_history)
        prompt = self._get_analysis_prompt_template()
        chain = prompt | self.llm | StrOutputParser()

        # 使用 stream 方法进行流式生成
        for chunk in chain.stream(
            {
                "jd_text": jd_text,
                "cv_text": cv_text,
                "conversation": conversation,
            }
        ):
            yield chunk
